Imports Microsoft.VisualBasic
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Xml
Imports System.ComponentModel
Imports System.Security.Permissions
Imports System.Runtime.Serialization
Imports System.Globalization
Imports C4F.DevKit.WebServices.Datasets

Namespace C4F.DevKit.WebServices
	''' <summary>
	''' Exposes search functionality on YouTube.com
	''' </summary>
	<ToolboxItem(True)> _
	Public Class YouTube
		Inherits C4FWebServicesBase
		#Region "Private members"
		''' <summary>
		''' Base Url
		''' </summary>
        Private Shared baseUrl As String = My.Resources.YouTubeBaseUrl
		''' <summary>
		''' Developer id required to use the api
		''' </summary>
		Private developerId_Renamed As String = String.Empty

		#End Region

		#Region "Accessors"
		''' <summary>
		''' Gets/sets Developer id.
		''' </summary>
		<Category("YouTube"), Description("Gets/sets Developer id.")> _
		Public Property DeveloperId() As String
			Get
				If Me.developerId_Renamed Is Nothing Then
					Return String.Empty
				Else
					Return Me.developerId_Renamed.Trim()
				End If
			End Get
			Set(ByVal value As String)
				If value Is Nothing Then
					Me.developerId_Renamed = String.Empty
				Else
					Me.developerId_Renamed = value.Trim()
				End If
			End Set
		End Property

		#End Region

		#Region "Constructors"
		''' <summary>
		''' Initializes a new object of YouTube class
		''' </summary>
		Public Sub New()
		End Sub
		''' <summary>
		''' Initializes a new object of YouTube class with a developer id
		''' </summary>
		''' <param name="developerId">Developer id</param>
        Public Sub New(ByVal developerId As String)
            Me.developerId_Renamed = developerId
        End Sub

		#End Region

		#Region "Public functions"
		''' <summary>
		''' Lists all videos that have the specified tag
		''' </summary>
		''' <param name="tag">The tag to search for</param>
		''' <param name="numberOfPagesForResult">The "page" of results you want to retrieve</param>
		''' <param name="resultsPerPage">No. of results per page</param>
		''' <returns>A list of videos having the specified tag</returns>
		''' <exception cref="InvalidOperationException">The Developer Key is not set</exception>
		''' <exception cref="YouTubeException">YouTube Web service has raised an error</exception>
		Public Function GetVideoListByTag(ByVal tag As String, ByVal numberOfPagesForResult As Nullable(Of Integer), ByVal resultsPerPage As Nullable(Of Integer)) As List(Of VideoData)
			If String.IsNullOrEmpty(Me.DeveloperId) Then
                Throw New InvalidOperationException(My.Resources.ErrorDeveloperKeyNotSet)
			End If

			Dim requestUrl As String = Me.GetQueryString(tag, numberOfPagesForResult, resultsPerPage)

			' Get YouTube responses
			Dim responseData As String = MyBase.GetQueryResponse(requestUrl)

			' Parse the data and return results
			Return YouTube.ParseResponseXml(responseData)
		End Function

		''' <summary>
		''' Lists all videos that have the specified tag
		''' </summary>
		''' <param name="tag">The tag to search for</param>
		''' <param name="numberOfPagesForResult">The "page" of results you want to retrieve</param>
		''' <param name="resultsPerPage">No. of results per page</param>
		''' <returns>Dataset containing the list of videos having the specified tag</returns>
		''' <exception cref="InvalidOperationException">The Developer Key is not set</exception>
		''' <exception cref="YouTubeException">YouTube Web service has raised an error</exception>
		Public Function GetVideoListByTag_DS(ByVal tag As String, ByVal numberOfPagesForResult As Nullable(Of Integer), ByVal resultsPerPage As Nullable(Of Integer)) As DS_YouTube
			Dim videoList As List(Of VideoData) = GetVideoListByTag(tag, numberOfPagesForResult, resultsPerPage)
			Dim dataset As DS_YouTube = New DS_YouTube()
			For Each video As VideoData In videoList
				Dim videoDetailsRow As DS_YouTube.VideoDetailsRow = dataset.VideoDetails.AddVideoDetailsRow(video.VideoDetails.Author, video.VideoDetails.LengthInSeconds, video.VideoDetails.ViewCount, video.VideoDetails.UploadTime, video.VideoDetails.CommentCount)

				Dim ratingRow As DS_YouTube.RatingRow = dataset.Rating.AddRatingRow(video.VideoDetails.Rating.NumberOfStars, video.VideoDetails.Rating.NumberOfVotes)

				dataset.VideoData.AddVideoDataRow(video.Id, video.Title, video.Description, video.Tags, video.Url, video.ThumbnailUrl, videoDetailsRow, ratingRow)
			Next video
			Return dataset
		End Function

		#End Region

		#Region "Private functions"

		''' <summary>
		''' Get query string based of specified parameters
		''' </summary>
		''' <param name="tag">The tag to search for</param>
		''' <param name="numberOfPagesForResult">The "page" of results you want to retrieve</param>
		''' <param name="resultsPerPage">No. of results per page</param>
		''' <returns>Request Url</returns>
		Private Function GetQueryString(ByVal tag As String, ByVal numberOfPagesForResult As Nullable(Of Integer), ByVal resultsPerPage As Nullable(Of Integer)) As String
			Dim queryBuilder As StringBuilder = New StringBuilder(YouTube.baseUrl)

			' Add method name
			queryBuilder.Append("?method=youtube.videos.list_by_tag")

			' Add developer key
			queryBuilder.Append("&dev_id=")
			queryBuilder.Append(Me.DeveloperId)

			' Add tag to search
			queryBuilder.Append("&tag=")
			queryBuilder.Append(tag)

			' Add numberOfPagesForResult
			If numberOfPagesForResult.HasValue Then
				queryBuilder.Append("&page=")
				queryBuilder.Append(numberOfPagesForResult.Value)
			End If

			' Add resultsPerPage
			If resultsPerPage.HasValue Then
				queryBuilder.Append("&per_page=")
				queryBuilder.Append(resultsPerPage)
			End If
			Return queryBuilder.ToString()
		End Function

		''' <summary>
		''' Parses the response data and extracts the videos
		''' </summary>
		''' <param name="responseData">Response data</param>
		''' <returns>A list of videos matching the criteria</returns>
		Private Shared Function ParseResponseXml(ByVal responseData As String) As List(Of VideoData)
			Dim xmlDocument As XmlDocument = New XmlDocument()
			Dim videoDataList As List(Of VideoData) = Nothing

			xmlDocument.LoadXml(responseData)

			' Retrieve errors nodes
			Dim errorsNode As XmlNode = xmlDocument.DocumentElement.SelectSingleNode("error")
			If Not errorsNode Is Nothing Then
				Throw New YouTubeException(errorsNode.SelectSingleNode("code").InnerText, errorsNode.SelectSingleNode("description").InnerText)
			End If


			' Retrieve result nodes
			Dim videoNodes As XmlNodeList = xmlDocument.DocumentElement.SelectNodes("video_list/video")

			If videoNodes Is Nothing OrElse videoNodes.Count = 0 Then
				Return videoDataList
			End If

			videoDataList = New List(Of VideoData)(videoNodes.Count)

			For i As Integer = 0 To videoNodes.Count - 1
				Dim videoNode As XmlNode = videoNodes(i)
				Dim videoData As VideoData = New VideoData()
				videoData.VideoDetails = New VideoDetails()
				videoData.VideoDetails.Rating = New Rating()
				videoDataList.Add(videoData)

				' Populate VideoData object
				videoData.VideoDetails.Author = videoNode.SelectSingleNode("author").InnerText
				videoData.Id = videoNode.SelectSingleNode("id").InnerText
				videoData.Title = videoNode.SelectSingleNode("title").InnerText

				videoData.VideoDetails.LengthInSeconds = Integer.Parse(videoNode.SelectSingleNode("length_seconds").InnerText, CultureInfo.InvariantCulture)

				videoData.VideoDetails.Rating.NumberOfStars = Double.Parse(videoNode.SelectSingleNode("rating_avg").InnerText, CultureInfo.InvariantCulture)

				videoData.VideoDetails.Rating.NumberOfVotes = Integer.Parse(videoNode.SelectSingleNode("rating_count").InnerText, CultureInfo.InvariantCulture)


				videoData.Description = videoNode.SelectSingleNode("description").InnerText

				videoData.VideoDetails.ViewCount = Integer.Parse(videoNode.SelectSingleNode("view_count").InnerText, CultureInfo.InvariantCulture)

				videoData.VideoDetails.UploadTime = Long.Parse(videoNode.SelectSingleNode("upload_time").InnerText, CultureInfo.InvariantCulture)

				videoData.VideoDetails.CommentCount = Integer.Parse(videoNode.SelectSingleNode("comment_count").InnerText, CultureInfo.InvariantCulture)

				videoData.Tags = videoNode.SelectSingleNode("tags").InnerText

				videoData.Url = videoNode.SelectSingleNode("url").InnerText

				videoData.ThumbnailUrl = videoNode.SelectSingleNode("thumbnail_url").InnerText

			Next i
			Return videoDataList
		End Function
		#End Region
	End Class

	''' <summary>
	''' Represents a video on YouTube.com
	''' </summary>
	Public Class VideoData
		#Region "Private Members"
		''' <summary>
		''' Video id
		''' </summary>
		Private id_Renamed As String
		''' <summary>
		''' Title of the video
		''' </summary>
		Private title_Renamed As String
		''' <summary>
		''' The video description
		''' </summary>
		Private description_Renamed As String
		''' <summary>
		''' Tags associated with the video
		''' </summary>
		Private tags_Renamed As String
		''' <summary>
		''' Url of the video
		''' </summary>
		Private url_Renamed As String
		''' <summary>
		''' Url of the thumbnail
		''' </summary>
		Private thumbNailUrl_Renamed As String
		''' <summary>
		''' Video Details
		''' </summary>
		Private videoDetails_Renamed As VideoDetails
		#End Region

		#Region "Public Properties"
		''' <summary>
		''' Video id
		''' </summary>
		Public Property Id() As String
			Get
				Return Me.id_Renamed
			End Get
			Set(ByVal value As String)
				Me.id_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Title of the video
		''' </summary>
		Public Property Title() As String
			Get
				Return Me.title_Renamed
			End Get
			Set(ByVal value As String)
				Me.title_Renamed = value
			End Set
		End Property
		''' <summary>
		''' The video description
		''' </summary>
		Public Property Description() As String
			Get
				Return Me.description_Renamed
			End Get
			Set(ByVal value As String)
				Me.description_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Tags associated with the video
		''' </summary>
		Public Property Tags() As String
			Get
				Return Me.tags_Renamed
			End Get
			Set(ByVal value As String)
				Me.tags_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Url of the video
		''' </summary>
		Public Property Url() As String
			Get
				Return Me.url_Renamed
			End Get
			Set(ByVal value As String)
				Me.url_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Url of the thumbnail
		''' </summary>
		Public Property ThumbnailUrl() As String
			Get
				Return Me.thumbNailUrl_Renamed
			End Get
			Set(ByVal value As String)
				Me.thumbNailUrl_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Video Details
		''' </summary>
		Public Property VideoDetails() As VideoDetails
			Get
				Return Me.videoDetails_Renamed
			End Get
			Set(ByVal value As VideoDetails)
				Me.videoDetails_Renamed = value
			End Set
		End Property
		#End Region
	End Class

	''' <summary>
	''' This class represents the video details
	''' </summary>
	Public Class VideoDetails
		#Region "Private Members"
		''' <summary>
		''' Author of the video
		''' </summary>
		Private author_Renamed As String
		''' <summary>
		''' Length of video
		''' </summary>
		Private lengthInSeconds_Renamed As Integer
		''' <summary>
		''' Rating that the video received
		''' </summary>
		Private rating_Renamed As Rating
		''' <summary>
		''' View counts
		''' </summary>
		Private viewCount_Renamed As Integer
		''' <summary>
		''' Upload time of the video
		''' </summary>
		Private uploadTime_Renamed As Long
		''' <summary>
		''' No. of comments this video have
		''' </summary>
		Private commentCount_Renamed As Integer
		#End Region

		#Region "Public Properties"
		''' <summary>
		''' Author of the video
		''' </summary>
		Public Property Author() As String
			Get
				Return Me.author_Renamed
			End Get
			Set(ByVal value As String)
				Me.author_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Length of video
		''' </summary>
		Public Property LengthInSeconds() As Integer
			Get
				Return Me.lengthInSeconds_Renamed
			End Get
			Set(ByVal value As Integer)
				Me.lengthInSeconds_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Rating that the video received
		''' </summary>
		Public Property Rating() As Rating
			Get
				Return Me.rating_Renamed
			End Get
			Set(ByVal value As Rating)
				Me.rating_Renamed = value
			End Set
		End Property
		''' <summary>
		''' View counts
		''' </summary>
		Public Property ViewCount() As Integer
			Get
				Return Me.viewCount_Renamed
			End Get
			Set(ByVal value As Integer)
				Me.viewCount_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Upload time of the video
		''' </summary>
		Public Property UploadTime() As Long
			Get
				Return Me.uploadTime_Renamed
			End Get
			Set(ByVal value As Long)
				Me.uploadTime_Renamed = value
			End Set
		End Property
		''' <summary>
		''' No. of comments this video have
		''' </summary>
		Public Property CommentCount() As Integer
			Get
				Return Me.commentCount_Renamed
			End Get
			Set(ByVal value As Integer)
				Me.commentCount_Renamed = value
			End Set
		End Property
		#End Region
	End Class

	''' <summary>
	''' Exception Class to handle YouTube exceptions
	''' </summary>
	<Global.System.Serializable> _
	Public Class YouTubeException
		Inherits Exception
		''' <summary>
		''' Error code from error node
		''' </summary>
		Private errorCode_Renamed As String
		''' <summary>
		''' Required to throw the specific exception
		''' </summary>
		Public Property ErrorCode() As String
			Get
				Return errorCode_Renamed
			End Get
			Set(ByVal value As String)
				Me.errorCode_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Initializes a new object of YouTubeException class
		''' </summary>
		Public Sub New()
		End Sub
		''' <summary>
		''' Initializes a new object of YouTubeException class
		''' </summary>
		''' <param name="errorCode">The code that represents the error</param>
		''' <param name="message">The message that describes the error</param>
        Public Sub New(ByVal errorCode As String, ByVal message As String)
            MyBase.New(message)
            Me.ErrorCode = errorCode
        End Sub
		''' <summary>
		''' Initializes a new object of YouTubeException class
		''' </summary>
		''' <param name="message">The message that describes the error</param>
		Public Sub New(ByVal message As String)
			MyBase.New(message)
		End Sub
		''' <summary>
		''' Initializes a new object of YouTubeException class
		''' </summary>
		''' <param name="errorCode">The code that represents the error</param>
		''' <param name="message">The message that describes the error</param>
		''' <param name="inner">The exception that is the cause of the current exception</param>
        Public Sub New(ByVal errorCode As String, ByVal message As String, ByVal inner As Exception)
            MyBase.New(message, inner)
            Me.ErrorCode = errorCode
        End Sub
		''' <summary>
		''' Initializes a new object of YouTubeException class
		''' </summary>
		''' <param name="message">The message that describes the error</param>
		''' <param name="inner">The exception that is the cause of the current exception</param>
		Public Sub New(ByVal message As String, ByVal inner As Exception)
			MyBase.New(message, inner)
		End Sub
		''' <summary>
		''' Initializes a new object of YouTubeException class
		''' </summary>
		''' <param name="info">The System.Runtime.Serialization.SerializationInfo that holds the serialized
		''' object data about the exception being thrown.</param>
		''' <param name="context">The System.Runtime.Serialization.StreamingContext that contains contextual
		''' information about the source or destination.</param>
		Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
			MyBase.New(info, context)
				Me.ErrorCode = info.GetString("errorCode")
		End Sub
		''' <summary>
		''' Sets the System.Runtime.Serialization.SerializationInfo
		''' with information about the exception.
		''' </summary>
		''' <param name="info">The System.Runtime.Serialization.SerializationInfo that holds the serialized
		''' object data about the exception being thrown.</param>
		''' <param name="context">The System.Runtime.Serialization.StreamingContext that contains contextual
		''' information about the source or destination.</param>
		<SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter := True)> _
		Public Overrides Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
			info.AddValue("errorCode", Me.ErrorCode)
			MyBase.GetObjectData(info, context)
		End Sub
	End Class
End Namespace